##from khronos.opt.base.solution import SolutionList
##from khronos.utils.fsm import StateMachine
##from khronos.utils import Clock, Logger, INF
##
### solver state names
##STOPPED     = "stopped"
##INITIALIZED = "initialized"
##PAUSED      = "paused"
##RUNNING     = "running"
##
### solver transition names
##INITIALIZE = "initialize"
##BOOTSTRAP  = "bootstrap"
##RUN        = "run"
##PAUSE      = "pause"
##STOP       = "stop"
##
##class Solver(StateMachine):
##    """Base class for minimization-problem solvers. Defines a minimal set of attributes that 
##    should be common to all solvers, such as instance, solution list, upper bound, a CPU tracker,
##    and a Logger object.
##    Basic methods include init(), run() for initialization and main loop of the solver, and the 
##    solve() method which is nothing but a shortcut to call the first two methods. Subclasses 
##    must always define a objective() method, since this is an essential part of any optimization 
##    problem."""
##    default_log = True
##    initial_state = STOPPED
##    transitions = {STOPPED:     {INITIALIZE: INITIALIZED},
##                   INITIALIZED: {BOOTSTRAP: PAUSED},
##                   PAUSED:      {STOP: STOPPED,
##                                 RUN: RUNNING},
##                   RUNNING:     {STOP: STOPPED,
##                                 PAUSE: PAUSED}}
##    final_states = [STOPPED]
##    
##    def __init__(self):
##        StateMachine.__init__(self)
##        self.instance = None
##        self.solutions = SolutionList()
##        self.upper_bound = INF
##        self.cpu = Clock()
##        self.log = Logger(enabled=self.default_log)
##        
##    def solve(self, instance=None, *args, **kwargs):
##        self.init(instance, *args, **kwargs)
##        return self.run()
##        
##    def init(self, instance=None, *args, **kwargs):
##        if self.state != INITIALIZED:
##            if self.state != STOPPED:
##                self.stop()
##            self.input(INITIALIZE, instance, *args, **kwargs)
##            
##    def run(self, *args, **kwargs):
##        with self.cpu.tracking():
##            if self.state == INITIALIZED:
##                self.input(BOOTSTRAP)
##            self.input(RUN, *args, **kwargs)
##        return self.solutions.best()
##        
##    def pause(self):
##        self.input(PAUSE)
##        
##    def stop(self):
##        self.input(STOP)
##        
##    def enter_initialized(self, instance=None):
##        self.log.info("Initializing %s..." % (self.__class__.__name__,))
##        if instance is not None:
##            self.instance = instance
##        self.solutions.clear()
##        self.upper_bound = INF
##        self.cpu.clear()
##        
##    def enter_paused(self):
##        pass
##        
##    def enter_running(self):
##        raise NotImplementedError()
##        
##    def enter_stopped(self):
##        pass
##        
##    def objective(self, solution):
##        """Given a solution, return its objective function value."""
##        raise NotImplementedError()
##        
##    def check_solution(self, solution, objective=None, **metadata):
##        """Check a given solution's objective function value. If it improves the solver's current 
##        upper bound, the solution is added to the solution list. This method returns a Solution 
##        object if the upper bound was updated, otherwise it returns None."""
##        if objective is None:
##            objective = self.objective(solution)
##        if objective < self.upper_bound:
##            return self.add_solution(solution, objective, **metadata)
##        return None
##        
##    def add_solution(self, solution, objective=None, **metadata):
##        """Add a solution to the solution list, and update the upper bound if necessary. This 
##        method creates and returns a Solution object."""
##        cpu = self.cpu.total
##        if objective is None:
##            objective = self.objective(solution)
##        if objective < self.upper_bound:
##            self.log.info("[%10.3fs] IMPROVEMENT %s -> %s" % (cpu, self.upper_bound, objective))
##            self.upper_bound = objective
##        return self.solutions.new(solution, objective, cpu=cpu, **metadata)
        
        
        
        
from khronos.opt.base.solution import SolutionList
from khronos.utils import Clock, Logger, INF

UNINITIALIZED = "uninitialized"
INITIALIZED = "initialized"
RUNNING = "running"
FINISHED = "finished"

class Solver(object):
    """Base class for minimization-problem solvers. Defines a minimal set of attributes that 
    should be common to all solvers, such as instance, solution list, upper bound, a CPU tracker,
    and a Logger object.
    Basic methods include init(), run() for initialization and main loop of the solver, and the 
    solve() method which is nothing but a shortcut to call the first two methods. Subclasses 
    must always define a objective() method, since this is an essential part of any optimization 
    problem."""
    default_log = True
    
    def __init__(self):
        self.instance = None
        self.solutions = SolutionList()
        self.upper_bound = INF
        self.cpu = Clock()
        self.log = Logger(enabled=self.default_log)
        self.state = UNINITIALIZED
        
    def __repr__(self):
        return "<%s %s @ %08x>" % (self.state, self.__class__.__name__, id(self))
        
    def solve(self, instance=None, *args, **kwargs):
        self.init(instance, *args, **kwargs)
        self.run()
        return self.solutions.best()
        
    def init(self, instance=None):
        self.log.info("Initializing %s..." % (self.__class__.__name__,))
        if instance is not None:
            self.instance = instance
        self.solutions.clear()
        self.upper_bound = INF
        self.cpu.clear()
        self.state = INITIALIZED
        
    def bootstrap(self):
        if self.state is UNINITIALIZED:
            raise ValueError("solver is not initialized")
        if self.state is INITIALIZED:
            self.setup()
            self.state = RUNNING
            
    def setup(self):
        pass
        
    def run(self):
        raise NotImplementedError()
        
    def finished(self):
        return self.state is FINISHED
        
    def objective(self, solution):
        """Given a solution, return its objective function value."""
        raise NotImplementedError()
        
    def check_solution(self, solution, objective=None, **metadata):
        """Check a given solution's objective function value. If it improves the solver's current 
        upper bound, the solution is added to the solution list. This method returns a Solution 
        object if the upper bound was updated, otherwise it returns None."""
        if objective is None:
            objective = self.objective(solution)
        if objective < self.upper_bound:
            return self.add_solution(solution, objective, **metadata)
        return None
        
    def add_solution(self, solution, objective=None, **metadata):
        """Add a solution to the solution list, and update the upper bound if necessary. This 
        method creates and returns a Solution object."""
        cpu = self.cpu.total
        if objective is None:
            objective = self.objective(solution)
        if objective < self.upper_bound:
            self.log.info("[%10.3fs] IMPROVEMENT %s -> %s" % (cpu, self.upper_bound, objective))
            self.upper_bound = objective
        return self.solutions.new(solution, objective, cpu=cpu, **metadata)
        